<html>
<head>
    <script src="OLLoader.js"></script>
    <script type="text/javascript">
    function setup() {
        window._xhr = OpenLayers.Request.XMLHttpRequest;
        var anon = new Function();
        OpenLayers.Request.XMLHttpRequest = function() {};
        OpenLayers.Request.XMLHttpRequest.prototype = {
            open: anon,
            setRequestHeader: anon,
            send: anon
        };
        OpenLayers.Request.XMLHttpRequest.DONE = 4;
    }
    function teardown() {
        OpenLayers.Request.XMLHttpRequest = window._xhr;
    }

    function test_defaultHeaders(t) {
        setup();
        t.plan(2);
        var config = {
            headers: {
                x: 'y'
            }
        };
        OpenLayers.Request.DEFAULT_CONFIG.headers = {
            foo: 'bar'
        };
        var proto = OpenLayers.Request.XMLHttpRequest.prototype;
        var issue = OpenLayers.Function.bind(OpenLayers.Request.issue,
                                             OpenLayers.Request);

        var headers = {};
        var _setRequestHeader = proto.setRequestHeader;
        proto.setRequestHeader = function(key, value) {
            headers[key] = value;
        };
        request = issue(config);
        t.eq(headers.foo, 'bar', "Header from DEFAULT_CONFIG set correctly");
        t.eq(headers.x, 'y', "Header from config set correctly");
        proto.setRequestHeader = _setRequestHeader;
        OpenLayers.Request.DEFAULT_CONFIG.headers = {};
        teardown();
    }
 
    function test_issue(t) {
        setup();

        t.plan(25);
        var request, config;
        var proto = OpenLayers.Request.XMLHttpRequest.prototype;
        var issue = OpenLayers.Function.bind(OpenLayers.Request.issue,
                                             OpenLayers.Request);

        // test that issue returns a new XMLHttpRequest - 1 test
        request = issue();
        t.ok(request instanceof OpenLayers.Request.XMLHttpRequest,
             "returns an XMLHttpRequest instance");
        
        // test that issue calls xhr.open with correct args from config - 5 tests
        var _open = proto.open;
        config = {
            method: "foo",
            url: "http://nowhere",
            async: "bar",
            user: "uncle",
            password: "sam"
        };
        proto.open = function(method, url, async, user, password) {
            t.eq(method, config.method, "open called with correct method");
            t.eq(url, config.url, "open called with correct url");
            t.eq(async, config.async, "open called with correct async");
            t.eq(user, config.user, "open called with correct user");
            t.eq(password, config.password, "open called with correct password");
        };
        request = issue(config);
        
        // test that params are serialized as query string - 1 test
        config = {
            method: "GET",
            url: "http://example.com/",
            params: {"foo": "bar"}
        };
        proto.open = function(method, url, async, user, password) {
            t.eq(url, config.url + "?foo=bar", "params serialized as query string");
        };
        request = issue(config);
        
        // test that empty params object doesn't produce query string - 1 test
        config = {
            method: "GET",
            url: "http://example.com/",
            params: {}
        };
        proto.open = function(method, url, async, user, password) {
            t.eq(url, config.url, "empty params doesn't produce query string");
        }
        request = issue(config);
        
        // test that query string doesn't get two ? separators
        config = {
            method: "GET",
            url: "http://example.com/?existing=query",
            params: {"foo": "bar"}
        };
        proto.open = function(method, url, async, user, password) {
            t.eq(url, config.url + "&foo=bar", "existing query string gets extended with &");
        }
        request = issue(config);
        
        // test that query string doesn't get ? followed by &
        config = {
            method: "GET",
            url: "http://example.com/service?",
            params: {"foo": "bar"}
        };
        proto.open = function(method, url, async, user, password) {
            t.eq(url, config.url + "foo=bar", "existing query string ending with ? gets extended without &");
        }
        request = issue(config);
        
        // reset open method
        proto.open = _open;
        
        // test that headers are correctly set - 6 tests
        var _setRequestHeader = proto.setRequestHeader;
        config = {
            headers: {
                foo: "bar",
                chicken: "soup",
                // This checks whether the autoadded 'X-Requested-With'-header
                // can be overridden, even though the given key here is spelled
                // in lowercase. 
                'x-requested-with': 'humpty'
            }
        };
        // we also track how often setRequestHeader is being called, it should
        // be called once for every header, even with the above defined
        // custom 'x-requested-with' header which we usually autoadd.
        // If the numbers match, we make sure to not send duplicate headers like
        //   x-requested-with: humpty         AND
        //   X-Requested-With: XMLHttpRequest
        var actualSetHeaderCnt = 0;
        var expectedSetHeaderCnt = 3; // and not four!
        proto.setRequestHeader = function(key, value) {
            actualSetHeaderCnt++;
            t.ok(key in config.headers, "setRequestHeader called with key: " + key);
            t.eq(value, config.headers[key], "setRequestHeader called with correct value: " + value);
        };
        request = issue(config);
        
        t.eq(actualSetHeaderCnt, expectedSetHeaderCnt, 'A custom "x-requested-with" header overrides the default "X-Requested-With" header.');

        proto.setRequestHeader = _setRequestHeader;
        
        // test that callback is called (no scope) - 1 test
        var unbound = function(request) {
            t.ok(request instanceof OpenLayers.Request.XMLHttpRequest,
                 "unbound callback called with xhr instance");
        }
        config = {
            callback: unbound
        };
        request = issue(config);
        request.readyState = OpenLayers.Request.XMLHttpRequest.DONE;
        request.onreadystatechange();

        // test that callback is called (with scope) - 2 tests
        var obj = {};
        var bound = function(request) {
            t.ok(this === obj, "bound callback has correct scope");
            t.ok(request instanceof OpenLayers.Request.XMLHttpRequest,
                 "bound callback called with xhr instance");
        }
        config = {
            callback: bound,
            scope: obj
        };
        request = issue(config);
        request.readyState = 4;
        request.onreadystatechange();
        
        // test that optional success callback is only called with 200s and
        // failure is only called with non-200s
        var _send = proto.send;
        proto.send = function() {};
        
        config = {
            success: function(req) {
                t.ok(!req.status || (req.status >= 200 && req.status < 300),
                     "success callback called with " + req.status + " status");
            },
            failure: function(req) {
                t.ok(req.status && (req.status < 200 || req.status >= 300),
                     "failure callback called with " + req.status + " status");
            }
        };
        request = issue(config);
        request.readyState = 4;
        
        // mock up status 200 (1 test)
        request.status = 200;
        request.onreadystatechange();
        
        // mock up status 299 (1 test)
        request.status = 299;
        request.onreadystatechange();
        
        // mock up status 100 (1 test)
        request.status = 100;
        request.onreadystatechange();

        // mock up status 300 (1 test)
        request.status = 300;
        request.onreadystatechange();
        
        // mock up a status null (1 test)
        request.status = null;
        request.onreadystatechange();

        proto.send = _send;

        teardown();
    }
    
    function test_delayed_send(t) {
        t.plan(1);
        var proto = OpenLayers.Request.XMLHttpRequest.prototype;
        var _send = proto.send;
        
        // test that send is called with data - 1 test
        var config = {
            method: "PUT",
            data: "bling"
        };
        var got = {};
        proto.send = function(data) {
            got.data = data;
        }
        OpenLayers.Request.issue(config);
        
        t.delay_call(1, function() {
            t.eq(got.data, config.data, "proper data sent");
            proto.send = _send;
        });        
        
    }
    
    function test_GET(t) {
        t.plan(1);
        var _issue = OpenLayers.Request.issue;
        OpenLayers.Request.issue = function(config) {
            t.eq(config.method, "GET", "calls issue with correct method");
        }
        OpenLayers.Request.GET();
        OpenLayers.Request.issue = _issue;
    }
    function test_POST(t) {
        t.plan(1);
        var _issue = OpenLayers.Request.issue;
        OpenLayers.Request.issue = function(config) {
            t.eq(config.method, "POST", "calls issue with correct method");
        }
        OpenLayers.Request.POST();
        OpenLayers.Request.issue = _issue;
    }
    function test_PUT(t) {
        t.plan(1);
        var _issue = OpenLayers.Request.issue;
        OpenLayers.Request.issue = function(config) {
            t.eq(config.method, "PUT", "calls issue with correct method");
        }
        OpenLayers.Request.PUT();
        OpenLayers.Request.issue = _issue;
    }
    function test_DELETE(t) {
        t.plan(1);
        var _issue = OpenLayers.Request.issue;
        OpenLayers.Request.issue = function(config) {
            t.eq(config.method, "DELETE", "calls issue with correct method");
        }
        OpenLayers.Request.DELETE();
        OpenLayers.Request.issue = _issue;
    }
    function test_HEAD(t) {
        t.plan(1);
        var _issue = OpenLayers.Request.issue;
        OpenLayers.Request.issue = function(config) {
            t.eq(config.method, "HEAD", "calls issue with correct method");
        }
        OpenLayers.Request.HEAD();
        OpenLayers.Request.issue = _issue;
    }
    function test_OPTIONS(t) {
        t.plan(1);
        var _issue = OpenLayers.Request.issue;
        OpenLayers.Request.issue = function(config) {
            t.eq(config.method, "OPTIONS", "calls issue with correct method");
        }
        OpenLayers.Request.OPTIONS();
        OpenLayers.Request.issue = _issue;
    }
    
    function test_events_success(t) {
        
        t.plan(5);

        var events = [];
        function listener(event) {
            events.push(event);
        }
        
        // set up event listeners
        OpenLayers.Request.events.on({
            complete: listener,
            success: listener,
            failure: listener
        });

        // issue a request that succeeds
        OpenLayers.Request.GET({
            url: ".", params: {bar: "baz"}, async: false
        });
        t.eq(events.length, 2, "two events logged");
        t.eq(events[0].type, "complete", "first event is complete");
        t.eq(events[1].type, "success", "second event is success");
        t.ok(events[1].config, "success listener sent config");
        t.eq(events[1].requestUrl, ".?bar=baz", "success listener sent config.url");

        // remove event listeners
        OpenLayers.Request.events.un({
            complete: listener,
            success: listener,
            failure: listener
        });
        
    }

    function test_events_failure(t) {
        
        t.plan(5);

        var events = [];
        function listener(event) {
            events.push(event);
        }
        
        // set up event listeners
        OpenLayers.Request.events.on({
            complete: listener,
            success: listener,
            failure: listener
        });

        // issue a request that succeeds
        OpenLayers.Request.GET({
            url: "foo", params: {bar: "baz"}, async: false
        });
        t.eq(events.length, 2, "two events logged");
        t.eq(events[0].type, "complete", "first event is complete");
        t.eq(events[1].type, "failure", "second event is failure");
        t.ok(events[1].config, "failure listener sent config");
        t.eq(events[1].requestUrl, "foo?bar=baz", "failure listener sent requestUrl");

        // remove event listeners
        OpenLayers.Request.events.un({
            complete: listener,
            success: listener,
            failure: listener
        });
        
    }

    function test_ProxyHost(t) {
        t.plan(5);

        /*
         * Setup
         */

        setup();

        var expectedURL;

        var _ProxyHost = OpenLayers.ProxyHost;

        var proto = OpenLayers.Request.XMLHttpRequest.prototype;
        var _open = proto.open;
        var log = [];
        var port;
        proto.open = function(method, url, async, user, password) {
            log.push(url);
        };

        /*
         * Test
         */

        // 2 tests
        log = [];
        OpenLayers.ProxyHost = "http://fooproxy/?url=";
        expectedURL = "http://fooproxy/?url=http%3A%2F%2Fbar%3Fk1%3Dv1%26k2%3Dv2";        
        OpenLayers.Request.GET({url: "http://bar?k1=v1&k2=v2"});
        t.eq(log.length, 1, "[1] XHR.open called once");
        t.eq(log[0], expectedURL, "[1] the URL used for XHR is correct (" + log[0] + ")");
        
        // 1 test
        log = [];
        OpenLayers.ProxyHost = "http://fooproxy/?url=";
        port = window.location.port ? ':'+window.location.port : '';
        expectedURL = window.location.protocol+"//"+window.location.hostname+port+"/service";
        OpenLayers.Request.GET({url: expectedURL});
        t.eq(log[0], expectedURL, "[2] proxy is not used when requesting the same server");
        
        // 2 tests
        log = [];
        OpenLayers.ProxyHost = function(url) {
            var p = OpenLayers.Util.getParameters(url);
            var p = OpenLayers.Util.getParameterString(p);
            return "http://barproxy/?" + p;
        };
        expectedURL = "http://barproxy/?k1=v1&k2=v2";
        OpenLayers.Request.GET({url: "http://bar?k1=v1&k2=v2"});
        t.eq(log.length, 1, "[3] XHR.open called once");
        t.eq(log[0], expectedURL, "[3] the URL used for XHR is correct (" + log[0] + ")");

        /*
         * Teardown
         */

        OpenLayers.Request.XMLHttpRequest.prototype.open = _open;
        OpenLayers.ProxyHost = _ProxyHost;
        teardown();
    }

    function test_abort(t) {

        t.plan(0);

        var sendCalled;

        // set up

        var _open = OpenLayers.Request.XMLHttpRequest.prototype.open;
        OpenLayers.Request.XMLHttpRequest.prototype.open = function() {
            this.readyState = OpenLayers.Request.XMLHttpRequest.OPENED;
        };

        var _setRequestHeader = OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader;
        OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = function() {};

        var _send = OpenLayers.Request.XMLHttpRequest.prototype.send;
        OpenLayers.Request.XMLHttpRequest.prototype.send = function() {
            sendCalled = true;
        };

        // test

        sendCalled = false;
        OpenLayers.Request.issue().abort();

        t.delay_call(0.5, function() {
            if (sendCalled) {
                t.fail("Send should not be called because request is aborted");
            }

            // tear down
            OpenLayers.Request.XMLHttpRequest.prototype.open = _open;
            OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = _setRequestHeader;
            OpenLayers.Request.XMLHttpRequest.prototype.send = _send;
        });
    }

    function test_abort2(t) {
        t.plan(0);
        var fail = false;
        OpenLayers.Request.XMLHttpRequest.onsend = function(args) {
            fail = true;
        }
        t.delay_call(0.5, function() {
            if (fail === true) {
                t.fail("Send should not be called because request is aborted");
            }
            OpenLayers.Request.XMLHttpRequest.onsend = null;
        });
        var req = OpenLayers.Request.GET();
        req.abort();
    }
    
    function test_XRequestedWithHeaderAutoadded(t) {
        t.plan( 2 );
        
        var headerSet = false;
        var headerGot = '';
        var headerExpected = 'XMLHttpRequest';
        
        // save to be able to restore later
        var _setRequestHeader = OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader;
        
        OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = function(field, value) {
            if (field === 'X-Requested-With') {
                headerSet = true;
                headerGot = value;
            }
        };
        
        var req = OpenLayers.Request.issue({
            url: location.href,
            async: false
        });
                
        t.ok( headerSet, 'We call the method "setRequestHeader" to set a "X-Requested-With"-header' );
        t.eq( headerGot, headerExpected,  'The "X-Requested-With"-header is set to "' + headerExpected + '" as expected.' );
        
        // restore old setRequestHeader
        OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = _setRequestHeader;
    }
    </script>
</head>
<body>
</body>
</html>
